package hlPay

import (
	"bytes"
	"context"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"net/url"
	"strings"
	"sync"
	"time"

	"gitee.com/wangshh_123/cross-pay-go/src/config"
	"gitee.com/wangshh_123/cross-pay-go/src/database"
)

// min 返回两个整数中的较小值
func min(a, b int) int {
	if a < b {
		return a
	}
	return b
}

var (
	// 单例实例
	instance *HlPayClient
	once     sync.Once
	initErr  error
)

// HlPayConfig HlPay平台配置
type HlPayConfig struct {
	ClientID       string `json:"clientid" yaml:"clientid"`                 // 客户端ID
	ClientKey      string `json:"clientkey" yaml:"clientkey"`               // 客户端密钥
	ClientSecret   string `json:"clientSecret" yaml:"clientSecret"`         // 客户端密钥
	GatewayURL     string `json:"gateway_url" yaml:"gateway_url"`           // 网关地址
	RefreshTime    int    `json:"refresh_time" yaml:"refresh_time"`         // 刷新时间
	MinRefreshTime int    `json:"min_refresh_time" yaml:"min_refresh_time"` // 最小刷新间隔
}

// TokenInfo 存储token信息
type TokenInfo struct {
	Token            string `json:"token"`
	RefreshToken     string `json:"refreshToken"`
	ExpiresIn        int64  `json:"expiresIn"`        // 过期时间（秒）
	RefreshExpiresIn int64  `json:"refreshExpiresIn"` // 刷新token过期时间（秒）
	ExpiresAt        int64  `json:"expiresAt"`        // 过期时间戳（毫秒）
}

// HlPayClient hlPay平台客户端
// 用于处理与hlPay平台的所有交互
type HlPayClient struct {
	config     *HlPayConfig // 平台配置信息
	httpClient *http.Client // HTTP客户端
	utils      *HlPayUtils  // 工具类实例
	refreshMu  sync.Mutex   // 刷新互斥锁，防止并发刷新
	tokenCache *TokenCache  // 内存缓存
}

type TokenCache struct {
	token     string
	expiresAt int64
	mu        sync.RWMutex
}

func NewTokenCache() *TokenCache {
	return &TokenCache{}
}

func (tc *TokenCache) Get() (string, bool) {
	tc.mu.RLock()
	defer tc.mu.RUnlock()

	if tc.token == "" || time.Now().UnixMilli() >= tc.expiresAt {
		return "", false
	}
	return tc.token, true
}

func (tc *TokenCache) Set(token string, expiresAt int64) {
	tc.mu.Lock()
	defer tc.mu.Unlock()

	tc.token = token
	tc.expiresAt = expiresAt
}

// GetHlPayClient 获取HlPayClient单例实例
// 参数:
//   - config: 平台配置信息
//
// 返回:
//   - *HlPayClient: 客户端实例
//   - error: 初始化过程中的错误
func GetHlPayClient(config *HlPayConfig) *HlPayClient {
	once.Do(func() {
		//TODO 临时输出配置信息
		fmt.Printf("[HlPay] 配置信息:\n")
		fmt.Printf("  ClientID: %s\n", config.ClientID)
		fmt.Printf("  ClientKey: %s\n", config.ClientKey)
		fmt.Printf("  ClientSecret: %s\n", config.ClientSecret)
		fmt.Printf("  GatewayURL: %s\n", config.GatewayURL)
		fmt.Printf("  RefreshTime: %d\n", config.RefreshTime)
		fmt.Printf("  MinRefreshTime: %d\n", config.MinRefreshTime)
		fmt.Println("[HlPay] 配置信息输出完成")

		utils, err := NewHlPayUtils()
		if err != nil {
			initErr = fmt.Errorf("初始化HlPayUtils失败: %v", err)
			return
		}

		instance = &HlPayClient{
			config:     config,
			httpClient: &http.Client{Timeout: 30 * time.Second},
			utils:      utils,
			tokenCache: NewTokenCache(),
		}
	})

	if initErr != nil {
		panic(fmt.Sprintf("HlPayClient初始化失败: %v", initErr))
	}

	return instance
}

// GetToken 获取当前访问令牌
// 返回:
//   - string: 当前有效的访问令牌
func (c *HlPayClient) GetToken() (string, error) {
	fmt.Println("[HlPay] 开始获取token...")

	// 首先从内存缓存中获取
	if token, ok := c.tokenCache.Get(); ok {
		fmt.Println("[HlPay] ✅ 从内存缓存获取到有效token")
		return token, nil
	}

	fmt.Println("[HlPay] 内存缓存中没有token，尝试从Redis获取...")
	// 如果内存缓存中没有，从Redis获取
	tokenInfo, err := c.getTokenFromRedis()
	if err != nil {
		fmt.Println("[HlPay] Redis中没有token，需要获取新token")
		// Redis中没有，获取新token
		return c.refreshToken()
	}

	// 检查token是否过期
	now := time.Now().UnixMilli()
	if tokenInfo.ExpiresAt <= now {
		fmt.Printf("[HlPay] Redis中的token已过期，过期时间: %d，当前时间: %d\n", tokenInfo.ExpiresAt, now)
		// token已过期，刷新token
		return c.refreshToken()
	}

	fmt.Println("[HlPay] ✅ 从Redis获取到有效token")
	// token有效，更新内存缓存并返回
	c.tokenCache.Set(tokenInfo.Token, tokenInfo.ExpiresAt)
	fmt.Println("[HlPay] ✅ Token已更新到内存缓存")
	return tokenInfo.Token, nil
}

// refreshToken 刷新token
// 返回:
//   - string: 新的访问令牌
//   - error: 刷新过程中的错误
func (c *HlPayClient) refreshToken() (string, error) {
	fmt.Println("[HlPay] 开始刷新token...")

	c.refreshMu.Lock()
	defer c.refreshMu.Unlock()

	// 再次检查内存缓存
	if token, ok := c.tokenCache.Get(); ok {
		fmt.Println("[HlPay] 内存缓存中已有有效token，跳过刷新")
		return token, nil
	}

	fmt.Println("[HlPay] 调用GetAccessToken获取新token...")
	// 获取新token
	token, refreshToken, expiresIn, refreshExpiresIn, err := c.utils.GetAccessToken()
	if err != nil {
		fmt.Printf("[HlPay] ❌ 获取访问令牌失败: %v\n", err)
		return "", fmt.Errorf("获取访问令牌失败: %v", err)
	}

	fmt.Printf("[HlPay] ✅ 成功获取新token\n")
	// 安全地打印token信息，避免slice bounds错误
	if len(token) > 20 {
		fmt.Printf("[HlPay] Token: %s...\n", token[:20])
	} else {
		fmt.Printf("[HlPay] Token: %s\n", token)
	}

	if len(refreshToken) > 20 {
		fmt.Printf("[HlPay] RefreshToken: %s...\n", refreshToken[:20])
	} else {
		fmt.Printf("[HlPay] RefreshToken: %s\n", refreshToken)
	}
	fmt.Printf("[HlPay] ExpiresIn: %d秒\n", expiresIn)
	fmt.Printf("[HlPay] RefreshExpiresIn: %d秒\n", refreshExpiresIn)

	// 确保expiresIn是正数
	if expiresIn <= 0 {
		expiresIn = 604799 // 默认7天（根据配置）
		fmt.Printf("[HlPay] 使用默认过期时间: %d秒\n", expiresIn)
	}

	// 计算过期时间戳（毫秒）
	now := time.Now()
	expiresAt := now.Add(time.Duration(expiresIn) * time.Second).UnixMilli()
	fmt.Printf("[HlPay] Token过期时间戳: %d\n", expiresAt)

	// 构造token信息
	tokenInfo := TokenInfo{
		Token:            token,
		RefreshToken:     refreshToken,
		ExpiresIn:        expiresIn,
		RefreshExpiresIn: refreshExpiresIn,
		ExpiresAt:        expiresAt,
	}

	// 将token信息存储到Redis
	tokenJSON, err := json.Marshal(tokenInfo)
	if err != nil {
		fmt.Printf("[HlPay] ❌ 序列化token信息失败: %v\n", err)
		return "", fmt.Errorf("序列化token信息失败: %v", err)
	}

	fmt.Println("[HlPay] 将token信息存储到Redis...")
	// 使用Redis存储token信息
	err = database.RedisClient.Set(context.Background(), "hlpay:token", tokenJSON, time.Duration(expiresIn)*time.Second).Err()
	if err != nil {
		fmt.Printf("[HlPay] ❌ 存储token到Redis失败: %v\n", err)
		return "", fmt.Errorf("存储token到Redis失败: %v", err)
	}
	fmt.Println("[HlPay] ✅ Token已成功存储到Redis")

	// 更新内存缓存
	c.tokenCache.Set(token, expiresAt)
	fmt.Println("[HlPay] ✅ Token已更新到内存缓存")

	if config.GlobalConfig.Server.Mode == "debug" {
		fmt.Printf("[HlPay] 新token已保存，过期时间: %d秒\n", expiresIn)
	}

	fmt.Println("[HlPay] Token刷新完成！")
	return token, nil
}

// doRequest 执行HTTP请求
// 参数:
//   - ctx: 上下文
//   - method: HTTP方法
//   - path: 请求路径
//   - body: 请求体
//
// 返回:
//   - []byte: 响应体
//   - error: 错误信息
func (c *HlPayClient) doRequest(ctx context.Context, method, path string, body interface{}) ([]byte, error) {
	// 获取token
	token, err := c.GetToken()
	if err != nil {
		return nil, fmt.Errorf("获取token失败: %v", err)
	}

	// 构造完整URL
	url := fmt.Sprintf("%s%s", c.config.GatewayURL, path)
	fmt.Printf("[HLPay doRequest] 请求URL: %s\n", url)
	fmt.Printf("[HLPay doRequest] 请求方法: %s\n", method)

	var reqBody []byte
	if body != nil {
		reqBody, err = json.Marshal(body)
		if err != nil {
			return nil, fmt.Errorf("序列化请求体失败: %v", err)
		}
		fmt.Printf("[HLPay doRequest] 请求体: %s\n", string(reqBody))
	}

	// 创建请求
	req, err := http.NewRequestWithContext(ctx, method, url, bytes.NewBuffer(reqBody))
	if err != nil {
		return nil, fmt.Errorf("创建请求失败: %v", err)
	}

	// 设置请求头
	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("Accept", "*/*")
	req.Header.Set("Authorization", token)
	req.Header.Set("clientId", c.config.ClientID)
	req.Header.Set("User-Agent", "Apifox/1.0.0 (https://apifox.com)")

	fmt.Printf("[HLPay doRequest] 请求头:\n")
	fmt.Printf("  - Content-Type: %s\n", req.Header.Get("Content-Type"))
	fmt.Printf("  - Accept: %s\n", req.Header.Get("Accept"))
	fmt.Printf("  - Authorization: %s...\n", token[:min(len(token), 20)])
	fmt.Printf("  - clientId: %s\n", req.Header.Get("clientId"))
	fmt.Printf("  - User-Agent: %s\n", req.Header.Get("User-Agent"))

	// 执行请求
	resp, err := c.httpClient.Do(req)
	if err != nil {
		fmt.Printf("[HLPay doRequest] 执行请求失败: %v\n", err)
		return nil, fmt.Errorf("执行请求失败: %v", err)
	}
	defer resp.Body.Close()

	fmt.Printf("[HLPay doRequest] 响应状态码: %d\n", resp.StatusCode)
	fmt.Printf("[HLPay doRequest] 响应头:\n")
	for key, values := range resp.Header {
		fmt.Printf("  - %s: %v\n", key, values)
	}

	// 读取响应
	respBody, err := io.ReadAll(resp.Body)
	if err != nil {
		fmt.Printf("[HLPay doRequest] 读取响应失败: %v\n", err)
		return nil, fmt.Errorf("读取响应失败: %v", err)
	}

	fmt.Printf("[HLPay doRequest] 响应体: %s\n", string(respBody))

	// 检查HTTP状态码
	if resp.StatusCode != http.StatusOK {
		fmt.Printf("[HLPay doRequest] HTTP请求失败，状态码: %d, 响应: %s\n", resp.StatusCode, string(respBody))
		return nil, fmt.Errorf("HTTP请求失败，状态码: %d, 响应: %s", resp.StatusCode, string(respBody))
	}

	return respBody, nil
}

// Get 执行GET请求
func (c *HlPayClient) Get(ctx context.Context, path string) ([]byte, error) {
	return c.doRequest(ctx, "GET", path, nil)
}

// Post 执行POST请求
func (c *HlPayClient) Post(ctx context.Context, path string, body interface{}) ([]byte, error) {
	return c.doRequest(ctx, "POST", path, body)
}

// Put 执行PUT请求
func (c *HlPayClient) Put(ctx context.Context, path string, body interface{}) ([]byte, error) {
	return c.doRequest(ctx, "PUT", path, body)
}

// Delete 执行DELETE请求
func (c *HlPayClient) Delete(ctx context.Context, path string) ([]byte, error) {
	return c.doRequest(ctx, "DELETE", path, nil)
}

// PostForm executes a POST request with application/x-www-form-urlencoded data.
func (c *HlPayClient) PostForm(ctx context.Context, path string, data url.Values) ([]byte, error) {
	// 获取token
	token, err := c.GetToken()
	if err != nil {
		return nil, fmt.Errorf("获取token失败: %v", err)
	}

	// 构造完整URL
	fullURL := fmt.Sprintf("%s%s", c.config.GatewayURL, path)

	// 创建请求
	req, err := http.NewRequestWithContext(ctx, "POST", fullURL, strings.NewReader(data.Encode()))
	if err != nil {
		return nil, fmt.Errorf("创建请求失败: %v", err)
	}

	// 设置请求头
	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
	req.Header.Set("Accept", "*/*")
	req.Header.Set("Authorization", token)
	req.Header.Set("clientId", c.config.ClientID)
	req.Header.Set("User-Agent", "Apifox/1.0.0 (https://apifox.com)")

	// 执行请求
	resp, err := c.httpClient.Do(req)
	if err != nil {
		return nil, fmt.Errorf("执行请求失败: %v", err)
	}
	defer resp.Body.Close()

	// 读取响应
	respBody, err := io.ReadAll(resp.Body)
	if err != nil {
		return nil, fmt.Errorf("读取响应失败: %v", err)
	}

	// 检查HTTP状态码
	if resp.StatusCode != http.StatusOK {
		return nil, fmt.Errorf("HTTP请求失败，状态码: %d, 响应: %s", resp.StatusCode, string(respBody))
	}

	return respBody, nil
}

// GetForm executes a GET request with application/x-www-form-urlencoded data in the request body.
// This is non-standard but some APIs support it.
func (c *HlPayClient) GetForm(ctx context.Context, path string, data url.Values) ([]byte, error) {
	// 获取token
	token, err := c.GetToken()
	if err != nil {
		return nil, fmt.Errorf("获取token失败: %v", err)
	}

	// 构造完整URL
	fullURL := fmt.Sprintf("%s%s", c.config.GatewayURL, path)

	fmt.Printf("HlPay Request URL: %s\n", fullURL)
	fmt.Printf("HlPay Request Data: %s\n", data.Encode())

	// 创建请求
	req, err := http.NewRequestWithContext(ctx, "GET", fullURL, strings.NewReader(data.Encode()))
	if err != nil {
		return nil, fmt.Errorf("创建请求失败: %v", err)
	}

	// 设置请求头
	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
	req.Header.Set("Accept", "*/*")
	req.Header.Set("Authorization", token)
	req.Header.Set("clientId", c.config.ClientID)
	req.Header.Set("User-Agent", "Apifox/1.0.0 (https://apifox.com)")

	// 执行请求
	resp, err := c.httpClient.Do(req)
	if err != nil {
		return nil, fmt.Errorf("执行请求失败: %v", err)
	}
	defer resp.Body.Close()

	// 读取响应
	respBody, err := io.ReadAll(resp.Body)
	if err != nil {
		return nil, fmt.Errorf("读取响应失败: %v", err)
	}

	// 检查HTTP状态码
	if resp.StatusCode != http.StatusOK {
		return nil, fmt.Errorf("HTTP请求失败，状态码: %d, 响应: %s", resp.StatusCode, string(respBody))
	}

	return respBody, nil
}

// getTokenFromRedis 从Redis获取token信息
func (c *HlPayClient) getTokenFromRedis() (*TokenInfo, error) {
	tokenJSON, err := database.RedisClient.Get(context.Background(), "hlpay:token").Result()
	if err != nil {
		return nil, err
	}

	var tokenInfo TokenInfo
	if err := json.Unmarshal([]byte(tokenJSON), &tokenInfo); err != nil {
		return nil, fmt.Errorf("反序列化token信息失败: %v", err)
	}

	return &tokenInfo, nil
}
